#include <QDebug>
#include <QFileDialog>
#include <QFile>
#include <QTextStream>
#include <QMessageBox>
#include <QXmlStreamReader>
#include <QProcessEnvironment>
#include <QBoxLayout>
#include <QToolBar>
#include <QStackedWidget>
#include <QRandomGenerator>
#include <QIntValidator>
#include <TopoDS_Shape.hxx>
#include <AIS_Shape.hxx>
#include "QtWidgetsApplication1.h"
#include "Common.h"

QtWidgetsApplication1::QtWidgetsApplication1(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);
	
	ui.width->setText("50");
	ui.height->setText("70");
	ui.thickness->setText("30");

	ui.width->setValidator(new QIntValidator(20, 100, this));
	ui.height->setValidator(new QIntValidator(20, 100, this));
	ui.thickness->setValidator(new QIntValidator(20, 100, this));

	QWidget* occ_view = this->findChild<QWidget*>("occ_view");
	
	QVBoxLayout* aMainLayout = new QVBoxLayout(occ_view);
	aMainLayout->setContentsMargins(0, 0, 0, 0);

	QWidget* my3dVidget = new QWidget;
	QVBoxLayout* a3dLayout = new QVBoxLayout(my3dVidget);
	a3dLayout->setContentsMargins(0, 0, 0, 0);
	a3dLayout->setSpacing(0);

	this->myDocument3d = new DocumentCommon(this);
	this->myView3d = new View(myDocument3d->getContext(), true, my3dVidget);
	a3dLayout->addWidget(this->myView3d);

	QStackedWidget* myStackWidget = new QStackedWidget(this);
	aMainLayout->addWidget(myStackWidget);
	myStackWidget->addWidget(my3dVidget);

	this->setUpEventHandlers();
}

void QtWidgetsApplication1::resetEditValue(QLineEdit *edit)
{
	auto name = edit->objectName();
	auto text = this->parameters.value(name, "0");
	edit->setText(text);
}

void QtWidgetsApplication1::refreshUI()
{
	resetEditValue(ui.width);
	resetEditValue(ui.height);
	resetEditValue(ui.thickness);
}

void QtWidgetsApplication1::setUpEventHandlers()
{
	connect(ui.actionRun, &QAction::triggered, this, &QtWidgetsApplication1::onRunClicked2);
	connect(ui.actionLoad, &QAction::triggered, this, &QtWidgetsApplication1::onLoadClicked);
	connect(ui.actionSave, &QAction::triggered, this, &QtWidgetsApplication1::onSaveClicked);
	connect(ui.actionSave_As, &QAction::triggered, this, &QtWidgetsApplication1::onSaveAsClicked);

	QLineEdit* edits[] = { 
		ui.width, ui.height, ui.thickness
	};
	for (const QLineEdit* edit : edits) {
		connect(edit, &QLineEdit::textEdited, this, [=](const QString &value) { this->onEditChanged(edit); });
	}
}

void QtWidgetsApplication1::onEditChanged(const QLineEdit *edit)
{
	this->parameters.insert(edit->objectName(), edit->text());
	this->unsaved = true;
}

void QtWidgetsApplication1::fakeGenerateTclScript(const QString &occ_path, const QString &dst)
{
	// XXX: we just copy existing sample tcl script
	const QString source = occ_path + "\\samples\\tcl\\bottle.tcl";
	QFile::copy(source, dst);
}

TopoDS_Shape
MakeBottle(const Standard_Real myWidth, const Standard_Real myHeight, const Standard_Real myThickness);

void QtWidgetsApplication1::onRunClicked2()
{
	int width = ui.width->text().toInt();
	int height = ui.height->text().toInt();
	int thickness = ui.thickness->text().toInt();

	QApplication::setOverrideCursor(Qt::WaitCursor);
	TopoDS_Shape aBottle = MakeBottle(width, height, thickness);
	Handle(AIS_Shape) AISBottle = new AIS_Shape(aBottle);

	Handle(AIS_InteractiveContext) context = this->myDocument3d->getContext();
	context->RemoveAll(true);
	context->SetMaterial(AISBottle, Graphic3d_NameOfMaterial_Gold, Standard_False);
	context->SetDisplayMode(AISBottle, 1, Standard_False);
	context->Display(AISBottle, Standard_False);
	const Handle(AIS_InteractiveObject)& anIOAISBottle = AISBottle;
	context->SetSelected(anIOAISBottle, Standard_False);
	//emit selectionChanged();
	this->myView3d->fitAll();
	QApplication::restoreOverrideCursor();
}

void QtWidgetsApplication1::onRunClicked()
{
	const QString ENV_OCC_PATH = "OCC_PATH";
	const QString DRAWEXE = "DRAWEXE.exe";

	if (!QProcessEnvironment::systemEnvironment().contains(ENV_OCC_PATH))
	{
		QMessageBox::information(0, "run error",
			QString("Please set the environment variable \"").append(ENV_OCC_PATH).append("\"."));
		return;
	}

	const QString OCC_PATH = QProcessEnvironment::systemEnvironment().value(ENV_OCC_PATH);
	const QString myDrawDefault = OCC_PATH + "\\My_DrawDefault";
	const QString myDrawBat = OCC_PATH + "\\my_draw.bat";

	// 1. TODO: generate tcl script and save it to $myDrawDefault.
	// ...
	fakeGenerateTclScript(OCC_PATH, myDrawDefault);
	
	// 2. generate my_draw.bat, which set environment variable 'DRAWDEFAULT' to $myDrawDefault
	QString content;
	if (!readFromFile(OCC_PATH + "\\draw.bat", content)) {
		QMessageBox::information(0, "read error", "Read from file failed.");
		return;
	}

	QString myContent;
	QStringList lines = content.split(QRegExp("[\r\n]"));
	for each (QString line in lines) {
		if (0 == QString::compare(line, DRAWEXE, Qt::CaseInsensitive)) {
			// ignore
		} else {
			myContent.append(line).append('\n');
		}
	}

	QString envStr = QString("set \"DRAWDEFAULT=%1\"").arg(myDrawDefault);
	myContent.append(envStr).append('\n').append('\n').append(DRAWEXE).append('\n');

	writeToFile(myDrawBat, myContent);

	// 3. call my_draw.bat
	QProcess::execute(myDrawBat);
}

void QtWidgetsApplication1::onSaveClicked()
{
	if (loadedProjectFilePath.length() == 0) {
		onSaveAsClicked();
	} else {
		writeProjectFile(loadedProjectFilePath);
	}
}

void QtWidgetsApplication1::onSaveAsClicked()
{
	QString fileName = QFileDialog::getSaveFileName(this,
		tr("Save Project File"), "",
		tr("Project File (*").append(PROJECT_FILE_EXT).append(")"));

	if (writeProjectFile(fileName)) {
		loadedProjectFilePath = fileName;
	}
}

void QtWidgetsApplication1::onLoadClicked()
{
	QString fileName = QFileDialog::getOpenFileName(this,
		tr("Open Project File"), "", 
		tr("Project File (*").append(PROJECT_FILE_EXT).append(")"));

	if (readProjectFile(fileName)) {
		loadedProjectFilePath = fileName;
	}
}

void QtWidgetsApplication1::writeParamToXML(QXmlStreamWriter &stream, const QString &key, const QString &value)
{
	stream.writeStartElement("param");
	stream.writeAttribute("key", key);
	stream.writeAttribute("value", value);
	stream.writeEndElement();
}

bool QtWidgetsApplication1::writeProjectFile(QString path)
{
	qInfo(tr(">> write project file ").append(path).toStdString().c_str());

	QString xmlcontent;
	QXmlStreamWriter stream(&xmlcontent);
	stream.setAutoFormatting(true);

	stream.writeStartDocument();
	stream.writeStartElement("parameters");

	QHash<QString, QString>::iterator i;
	for (i = this->parameters.begin(); i != this->parameters.end(); ++i) {
		writeParamToXML(stream, i.key(), i.value());
	}

	stream.writeEndElement();
	stream.writeEndDocument();

	if (!writeToFile(path, xmlcontent)) {
		QMessageBox::information(0, "save error", "Write to file failed.");
		return false;
	}

	unsaved = false;

	return true;
}

static const char * ErrorStrings[] = { 
	"NoError", "UnexpectedElementError", "CustomError", "NotWellFormedError", "PrematureEndOfDocumentError"
};

bool QtWidgetsApplication1::readProjectFile(QString path)
{
	qInfo(tr(">> read project file ").append(path).toStdString().c_str());

	QString content;
	if (!readFromFile(path, content)) {
		QMessageBox::information(0, "read error", "Read from file failed.");
		return false;
	}
	
	QHash<QString, QString> hash;
	QXmlStreamReader xml(content);

	while (!xml.atEnd()) {
		xml.readNext();

		if (xml.isStartElement() && xml.name() == "param") {
			auto attrs = xml.attributes();
			auto key = attrs.value("key");
			auto value = attrs.value("value");

			hash.insert(key.toString(), value.toString());
		}
	}

	if (xml.hasError()) {
		QMessageBox::information(0, "xml error", ErrorStrings[xml.error()]);
		return false;
	}

	this->parameters = hash;

	this->refreshUI();

	return true;
}
